package com.hyt.it.ogt.ks.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.core.base.BaseServiceImpl;
import com.hyt.core.exception.NestedBusinessException;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.ks.api.kw.model.ObjectiveItemScoreDTO;
import com.hyt.it.ogt.ks.api.kw.model.QuestionAnalysisDTO;
import com.hyt.it.ogt.ks.api.kw.model.QuestionAnalysisRequestDTO;
import com.hyt.it.ogt.ks.config.ConfigManager;
import com.hyt.it.ogt.ks.config.KsBizRunTimeException;
import com.hyt.it.ogt.ks.enums.*;
import com.hyt.it.ogt.ks.enums.log.CandidateAnswerLogType;
import com.hyt.it.ogt.ks.enums.log.LogEventType;
import com.hyt.it.ogt.ks.feign.KwClient;
import com.hyt.it.ogt.ks.feign.TpsClient;
import com.hyt.it.ogt.ks.feign.model.*;
import com.hyt.it.ogt.ks.mapper.*;
import com.hyt.it.ogt.ks.model.dto.CandidateAnswerSimpleVO;
import com.hyt.it.ogt.ks.model.dto.VerifyAnswerOptionsDTO;
import com.hyt.it.ogt.ks.model.entity.*;
import com.hyt.it.ogt.ks.model.interview.*;
import com.hyt.it.ogt.ks.model.vo.*;
import com.hyt.it.ogt.ks.model.vo.paper.PItemDTO;
import com.hyt.it.ogt.ks.model.vo.paper.POptionDTO;
import com.hyt.it.ogt.ks.model.xyvo.*;
import com.hyt.it.ogt.ks.mq.model.CandidateInfoUpdateDTO;
import com.hyt.it.ogt.ks.service.*;
import com.hyt.it.ogt.ks.util.AnswerOptionUtils;
import com.hyt.it.ogt.ks.util.Constants;
import com.hyt.it.ogt.ks.util.KeyDefineCommon;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 考试答案详情表 服务实现类
 * </p>
 *
 * @author chengxh
 * @since 2020-06-17
 */
@Slf4j
@Service
public class CandidateAnswerServiceImpl extends BaseServiceImpl<CandidateAnswerMapper, CandidateAnswer>
        implements ICandidateAnswerService {
    @Resource
    private ICandidateProcessService iCandidateProcessService;
    @Resource
    private CandidateAnswerMapper candidateAnswerMapper;
    @Resource
    private KwClient kwClient;
    @Resource
    private IExamCacheService iExamCacheService;
    @Resource 
    private ConfigManager configManager;
    @Resource
    private ICandidatePaperService iCandidatePaperService;
    @Resource
    private ForceItemMapper forceItemMapper;
    @Resource
    private XyEvalLogMapper xyEvalLogMapper;
    @Resource
    private XyDayiLogMapper xyDayiLogMapper;
    @Resource
    private IPaperService iPaperService;
    @Resource 
    private XyJinyuLogMapper xyJinyuLogMapper;
    @Resource
    private TpsClient tpsClient;
    @Resource
    private ICandidateAnswerBackupService iCandidateAnswerBackupService;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private CandidateLogTaskMapper candidateLogTaskMapper;
    @Resource
    private IScoreService iScoreService;
    @Resource
    private ILogService iLogService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public PaperTopicItemListVO saveQuestion(CandidateQusAnsVO param, VerifyAnswerOptionsDTO verify, HttpServletRequest request) {
        //判断是什么类型的试卷
        if(Objects.equals(PaperTypeCode.NORMAL.getCode(),verify.getCandidatePaper().getPaperType())
                || Objects.equals(PaperTypeCode.XIAOTIJISHI.getCode(),verify.getCandidatePaper().getPaperType())) {
            this.saveAllQuestion(param, verify, request);
            return null;
        }else{
            return this.saveQuestionForCharacterTest(param, verify, request);
        }
    }

    /**
     * 如果存在迫选题，会返回二次迫选项数据
     * @param param
     * @param verify
     * @return
     */
    private List<PaperQuestionVO> saveAllQuestion(CandidateQusAnsVO param, VerifyAnswerOptionsDTO verify, HttpServletRequest request) {
        long s1 = System.currentTimeMillis();
        List<PaperQuestionVO> secondForceItemList = new ArrayList<>();
        //轨迹容器
        List<CandidateLog> logs = new ArrayList<CandidateLog>();
        //待更新考生答案容器
        List<CandidateAnswer> updateList = new ArrayList<>();
        //考试id
        String examId = param.getExamId();
        //考生id
        String candidateId = param.getCandidateId();
        //查看答案和解析状态
        Integer viewAnswerDescription = param.getViewAnswerDescription();
        //考生试卷
        CandidatePaper candidatePaper = verify.getCandidatePaper();

        // 最好的方式是前端传answer表id过来，需要改下前端，这样的话下面的查询都可以不用要，查询数据量也大也消耗内存产生gc
        Map<String, CandidateAnswer> ansMap = verify.getAnsMap();

        // 性格测试卷使用
        List<ForceItem> forceItemList = null;
        Map<String, ForceItem> forceItemMap = new HashMap<>();
        if (PaperTypeCode.CHARACTER.getCode().intValue() == candidatePaper.getPaperType()) {
            // 先加载所有的迫选题
            forceItemList = forceItemMapper.selectList(Wrappers.<ForceItem>lambdaQuery()
                    .select(ForceItem::getSecondEditOption, ForceItem::getDisturbOption, ForceItem::getParentItemId,
                            ForceItem::getSecondViewOption, ForceItem::getItemId, ForceItem::getDisturbOption)
                    .eq(ForceItem::getExamId, examId).eq(ForceItem::getCandidateId, candidateId)
                    .eq(ForceItem::getPaperId, candidatePaper.getPaperId())
                    .eq(ForceItem::getPaperVersion, candidatePaper.getPaperVersion()));
            for (ForceItem item : forceItemList) {
                forceItemMap.put(item.getParentItemId() + "_" + item.getDisturbOption(), item);
            }
        }

        // 保存答案
        for (QuestionAnswerVO questionAnswerVO : param.getAnswer()) {
            String itemType = questionAnswerVO.getItemType();
            if (PaperItemTypeCode.ITEM_TYPE_PICTURE_ANSWER_CODE.getCode().equalsIgnoreCase(itemType)) {
                continue;
            }
            String itemId = questionAnswerVO.getItemId();
            CandidateAnswer candidateAnswer = ansMap.get(itemId);
            if (StringUtils.isBlank(itemType) || StringUtils.isBlank(itemId)) {
                continue;
            }
            // 无答案
            if (questionAnswerVO.getUserAnswerList() == null || questionAnswerVO.getUserAnswerList().size() == 0) {
                // 没有作答
                if (PaperItemTypeCode.ITEM_TYPE_FILL_IN_BLANKS_CODE.getCode().equalsIgnoreCase(itemType)) {
                    // 把填空题所有的答案清空，前端传的格式不统一，蛋疼
                    this.clearFillInBlanksAnswer(examId, candidateId, candidatePaper.getPaperId(),
                            candidatePaper.getPaperVersion(), itemId, viewAnswerDescription, logs, candidateAnswer);
                } else {
                    if (!PaperItemTypeCode.ITEM_TYPE_COMPOUND_CODE.getCode().equalsIgnoreCase(itemType)
                            && !PaperItemTypeCode.ITEM_TYPE_CLOZE_FILL_IN_BLANKS_CODE.getCode().equalsIgnoreCase(itemType)//完形填空类似子小题，且子不题为单选，不进行空值保存
                            && !Objects.equals(Boolean.TRUE, verify.getIsSingleNull())
                            && !Objects.equals(Boolean.TRUE, verify.getIsJudgmentNull())) {
                        //增加判断当单选为空时不更新
                        CandidateAnswer a = this.updateAnswer(examId, candidateId, itemId, candidateAnswer, viewAnswerDescription,
                                "", questionAnswerVO.getAnswerTime(), logs);
                       if (a != null) {
                            updateList.add(a);
                       }
                    }
                    //轨迹增加为空的时候进行保存  单选+判断  匹配类似多选
                    /*if(PaperItemTypeCode.ITEM_TYPE_JUDGMENT_CODE.getCode().equalsIgnoreCase(itemType)
                            || PaperItemTypeCode.ITEM_TYPE_SINGLE_CODE.getCode().equalsIgnoreCase(itemType)
                            || PaperItemTypeCode.ITEM_TYPE_MATCHING_CODE.getCode().equalsIgnoreCase(itemType)){
                        logs.add(this.buildLog(examId, itemId, candidateId, LogEventType.ANSWER_LOG.getCode(),
                                CandidateAnswerLogType.ANSWER.getCode(), "",
                                questionAnswerVO.getAnswerTime(), itemType));
                    }*/
                }
            } else {
                // 有答案
                List<String> answeredList = new ArrayList<>();
                // 判断和多选、单选前端答案值（例如0,1,A,B,C...），该值为后端传给前端的optionValue字段
                if (PaperItemTypeCode.ITEM_TYPE_JUDGMENT_CODE.getCode().equalsIgnoreCase(itemType)
                        || PaperItemTypeCode.ITEM_TYPE_MULTIPLE_CODE.getCode().equalsIgnoreCase(itemType)
                        || PaperItemTypeCode.ITEM_TYPE_SINGLE_CODE.getCode().equalsIgnoreCase(itemType)
                        || PaperItemTypeCode.ITEM_TYPE_SHORT_ANSWER_CODE.getCode().equalsIgnoreCase(itemType)
                        || PaperItemTypeCode.ITEM_TYPE_PICTURE_ANSWER_CODE.getCode().equalsIgnoreCase(itemType)
                        || PaperItemTypeCode.ITEM_TYPE_MATCHING_CHILD_CODE.getCode().equalsIgnoreCase(itemType) //匹配 = 多选
                        || PaperItemTypeCode.ITEM_TYPE_CLOZE_FILL_IN_BLANKS_CHILD_CODE.getCode().equalsIgnoreCase(itemType) //完行真空 = 复合-单选
                        || PaperItemTypeCode.ITEM_TYPE_INDEFINITE_CODE.getCode().equalsIgnoreCase(itemType)/*不定向 = 多选*/
                        || PaperItemTypeCode.ITEM_TYPE_WEIGHT_CODE.getCode().equalsIgnoreCase(itemType)){
                    // 排序一下，保证我们的答案保存时是按照字母顺序排列的
                    if (!PaperItemTypeCode.ITEM_TYPE_PICTURE_ANSWER_CODE.getCode().equalsIgnoreCase(itemType)
                            && !PaperItemTypeCode.ITEM_TYPE_MATCHING_CODE.getCode().equalsIgnoreCase(itemType)) {//匹配题类似多选，但不能进行答案排序
                        questionAnswerVO.getUserAnswerList().sort(Comparator.comparing(AnswerVO::getUserAnswer));
                    }
                    questionAnswerVO.getUserAnswerList().forEach(item -> {
                        answeredList.add(item.getUserAnswer());
                    });
                    //多选题的时候处理重复选项
                    String userAnswerStr;
                    if(PaperItemTypeCode.ITEM_TYPE_MATCHING_CODE.getCode().equalsIgnoreCase(itemType)){
                        userAnswerStr = joinMatchingTypeAnswer(answeredList);
                    } else {
                        userAnswerStr = answeredList.size() > 0 ? StringUtils.join(answeredList, ",") : "";
                    }
                    if(PaperItemTypeCode.ITEM_TYPE_MULTIPLE_CODE.getCode().equalsIgnoreCase(itemType)){
                        userAnswerStr = AnswerOptionUtils.deleteSame(userAnswerStr);
                    }
                    CandidateAnswer a = this.updateAnswer(examId, candidateId, itemId, candidateAnswer, viewAnswerDescription,
                            userAnswerStr, questionAnswerVO.getAnswerTime(), logs);
                    if (a != null) {
                        updateList.add(a);
                    }
                } else if (PaperItemTypeCode.ITEM_TYPE_FORCE_CHOICE_CODE.getCode().equalsIgnoreCase(itemType)) {
                    // 处理迫选题-单选
                    String userAnswer = questionAnswerVO.getUserAnswerList().get(0).getUserAnswer();
                    // 根据作答答案，查询二次迫选项
                    ForceItem f = forceItemMap.get(itemId + "_" + userAnswer);

                    // 判断是否需要多次迫选
                    if (!f.getDisturbOption().equals(userAnswer) ||
                            f.getSecondEditOption().equalsIgnoreCase(f.getDisturbOption())) {
                        // 不需要二次迫选
                        CandidateAnswer a = this.updateAnswer(examId, candidateId, itemId, candidateAnswer, viewAnswerDescription,
                                userAnswer, questionAnswerVO.getAnswerTime(), logs);
                        if (a != null) {
                            updateList.add(a);
                        }
                    } else {
                        PaperQuestionVO paperQuestionVO = this.handleForceOption(itemId, f);
                        // 组装二次迫选数据
                        secondForceItemList.add(paperQuestionVO);
                        // 保存选中的迫选项答案
                        CandidateAnswer answerUpdate = CandidateAnswer.builder()
                                .forceAnswer(userAnswer).viewAnswerDescription(viewAnswerDescription).build();
                        answerUpdate.setId(ansMap.get(itemId).getId());
                        if (logs != null) {
                            logs.add(this.buildLog(examId, itemId, candidateId, LogEventType.ANSWER_LOG.getCode(),
                                    CandidateAnswerLogType.ANSWER.getCode(), userAnswer,
                                    questionAnswerVO.getAnswerTime(), itemType));
                        }
                        updateList.add(answerUpdate);
                    }

                } else if (PaperItemTypeCode.ITEM_TYPE_FILL_IN_BLANKS_CODE.getCode().equalsIgnoreCase(itemType)) {
                    // 填空
                    // 先把填空题所有的答案清空，前端传的格式不统一，蛋疼
                    this.clearFillInBlanksAnswer(examId, candidateId, candidatePaper.getPaperId(),
                            candidatePaper.getPaperVersion(), itemId, viewAnswerDescription, null, candidateAnswer);
                    questionAnswerVO.getUserAnswerList().forEach(obj -> {
                        if (obj != null) {
                            CandidateAnswer ans = ansMap.get(obj.getOptionId());
                            ans.setItemType(itemType);
                            // 2.4p4这一期按要求填空题小选项按大题总时长来显示
                            CandidateAnswer a = this.updateAnswer(examId, candidateId, obj.getOptionId(),
                                    ans, viewAnswerDescription, obj.getUserAnswer(), questionAnswerVO.getAnswerTime(), logs);
                            if (a != null) {
                                updateList.add(a);
                            }
                        }
                    });
                } else {
                    log.error("不支持的题型");
                }
            }
        }
        s1 = System.currentTimeMillis() - s1;
        // 手动清除数据
        /*ansList = null;
        forceItemMap = null;
        forceItemList = null;*/
        long s2 = System.currentTimeMillis();
        if(updateList.size() > 0) {
            super.updateBatchById(updateList);
        }
        s2 = System.currentTimeMillis() - s2;
        long s3 = System.currentTimeMillis();
        String ip=  iLogService.getIp(request);
        logs.forEach(item -> {
            item.setIp(ip);
        });
        if (logs.size() > 0 && (param.getSaveType() == null
                || (param.getSaveType() != null && SaveAnswerType.NORMAL.getCode().equalsIgnoreCase(param.getSaveType())))) {
            // 新轨迹的保存
            iLogService.saveAnswerLog(logs);
        } else if (logs.size() > 0 && Objects.equals(SaveAnswerType.TIMING.getCode(),param.getSaveType())){
            //定时保存轨迹入库
            candidateLogTaskMapper.batchSave(logs);
        }
        String timeMillis = "\n-----------------------------------------\n" +
                "耗时    任务名称\n" +
                s1 + "ms    " + "收集答案数据\n" +
                s2 + "ms    " + "批量更新答案\n" +
                (System.currentTimeMillis() - s3) + "ms    " + "保存轨迹\n" +
                "-----------------------------------------\n";
        log.info(timeMillis);
        return secondForceItemList;
    }

    /**
     * 匹配题答案拼接
     * @param answeredList
     * @return
     */
    private String joinMatchingTypeAnswer(List<String> answeredList){
        if(CollectionUtil.isEmpty(answeredList)){
            return "";
        }
        //匹配题需要把中间为""的选项也要拼接
        StringBuilder sb = new StringBuilder();
        for(String answer : answeredList){
            sb.append(answer).append(",");
        }
        String result = sb.toString();
        return result.substring(0,result.lastIndexOf(","));
    }

    /**
     * 针对填空题，清空该填空题所有答案
     * @param examId
     * @param candidateId
     * @param paperId
     * @param paperVersion
     * @param itemId
     */
    private void clearFillInBlanksAnswer(String examId, String candidateId, String paperId, String paperVersion,
                                         String itemId, Integer viewAnswerDescription, List<CandidateLog> logs, CandidateAnswer ans) {
        PItemDTO item = iPaperService.getPItem(paperId, paperVersion, itemId);
        item.getOptionMap().forEach((k, v) -> {
            this.updateAnswerByItemId(examId, candidateId, paperId, paperVersion, v.getOptionId(),
                    viewAnswerDescription, null, 0L, logs);

        });
    }

    private CandidateAnswer getCandidateAnswer(String examId, String candidateId, String paperId, String paperVersion,
                                               String itemId) {
        return this.lambdaQuery().select(CandidateAnswer::getItemMixSort, CandidateAnswer::getOptionMixSortRule)
                .eq(CandidateAnswer::getExamId, examId).eq(CandidateAnswer::getCandidateId, candidateId)
                .eq(CandidateAnswer::getItemId, itemId).eq(CandidateAnswer::getPaperId, paperId)
                .eq(CandidateAnswer::getPaperVersion, paperVersion).eq(CandidateAnswer::getDelFlag, false).one();
    }

    /**
     * 组装二次迫选数据
     * @param itemId
     * @param forceItem
     * @return
     */
    private PaperQuestionVO handleForceOption(String itemId, ForceItem forceItem){
        PaperQuestionVO paperQuestionVO = new PaperQuestionVO();
        List<TopicOptionVO> optionVOList = new ArrayList<>();
        paperQuestionVO.setItemId(itemId);
        paperQuestionVO.setOptionsDataList(optionVOList);
        String[] arr = forceItem.getSecondEditOption().split(",");
        for (String s : arr) {
            TopicOptionVO optionVO = new TopicOptionVO();
            optionVO.setOptionName(s);
            optionVO.setEdit(true);
            optionVOList.add(optionVO);
        }
        if(forceItem.getSecondViewOption() != null){
            arr = forceItem.getSecondViewOption().split(",");
            for (String s : arr) {
                TopicOptionVO optionVO = new TopicOptionVO();
                optionVO.setOptionName(s);
                optionVO.setEdit(false);
                optionVOList.add(optionVO);
            }
        }
        return paperQuestionVO;
    }

    private CandidateAnswer updateAnswer(String examId, String candidateId, String itemId, CandidateAnswer ans,
                                      Integer viewAnswerDescription, String userAnswer, Long answerTime, List<CandidateLog> logs){
        if(StringUtils.isBlank(itemId)){
            return null;
        }
        CandidateAnswer insert = CandidateAnswer.builder().viewAnswerDescription(viewAnswerDescription).build();
        if(ans.getObjectiveOrSubjective() && !PaperItemTypeCode.ITEM_TYPE_FILL_IN_BLANKS_CODE.getCode().equals(ans.getItemType())) {
            insert.setObjectiveAnswer(userAnswer);
        } else {
            insert.setSubjectiveAnswer(userAnswer);
        }
        insert.setId(ans.getId());
        insert.setAnswerTime(ans.getAnswerTime() + (answerTime == null ? 0 : answerTime));

        // 保存答案
        //baseMapper.updateAnswer(insert);
        if(logs != null){
            //logs.add(this.buildLog(examId, itemId, candidateId, LogEventType.ANSWER_LOG.getCode(), CandidateAnswerLogType.ANSWER.getCode(), userAnswer, StringUtils.isBlank(ans.getParentItemId()) ? answerTime : null, ans.getItemType()));
            logs.add(this.buildLog(examId, itemId, candidateId, LogEventType.ANSWER_LOG.getCode(),
                    CandidateAnswerLogType.ANSWER.getCode(), userAnswer, answerTime, ans.getItemType()));
        }
        return insert;
    }

    /**
     * 通过小题ID保存考生答案
     * @param examId
     * @param candidateId
     * @param itemId
     * @param userAnswer
     * @param logs
     */
    private void updateAnswerByItemId(String examId, String candidateId, String paperId, String paperVersion,
                                      String itemId, Integer viewAnswerDescription, String userAnswer,
                                      Long answerTime, List<CandidateLog> logs){
        LambdaQueryWrapper<CandidateAnswer> query = Wrappers.lambdaQuery();
        query.select(CandidateAnswer::getId, CandidateAnswer::getAnswerTime, CandidateAnswer::getItemType)
                .eq(CandidateAnswer::getExamId, examId).eq(CandidateAnswer::getCandidateId, candidateId)
                .eq(CandidateAnswer::getPaperId, paperId).eq(CandidateAnswer::getPaperVersion, paperVersion)
                .eq(CandidateAnswer::getDelFlag, false);
        if(StringUtils.isBlank(itemId)){
            return;
        }
        query.eq(CandidateAnswer::getItemId, itemId);
        CandidateAnswer candidateAnswer = CandidateAnswer.builder()
                .objectiveAnswer(userAnswer)
                .subjectiveAnswer(userAnswer)
                .viewAnswerDescription(viewAnswerDescription)
                .answerTime(answerTime == null ? 0 : answerTime)
                .build();
        CandidateAnswer answer = super.getOne(query);
        candidateAnswer.setId(answer.getId());
        candidateAnswer.setAnswerTime(answer.getAnswerTime() + (answerTime == null ? 0 : answerTime));
        // 保存答案
        baseMapper.updateAnswer(candidateAnswer);
        if(logs != null){
            logs.add(this.buildLog(examId, itemId, candidateId, LogEventType.ANSWER_LOG.getCode(),
                    CandidateAnswerLogType.ANSWER.getCode(), userAnswer, candidateAnswer.getAnswerTime(), answer.getItemType()));
        }
    }

    /**
     * 构建日志对象
     * @param examId
     * @param itemId
     * @param candidateId
     * @param type
     */
    private CandidateLog buildLog(String examId, String itemId, String candidateId, int eventType, int type,
                                  String operateContent, Long takeTime, String itemType){
        CandidateLog log = CandidateLog.builder()
                .id(UUIDUtils.randomUUID())
                .takeTime(takeTime)
                .examId(examId)
                .itemId(itemId)
                .eventType(eventType)
                .operateType(type)
                .operateContent(operateContent)
                .candidateId(candidateId)
                .itemType(itemType)
                .beginTime(LocalDateTime.now()).build();
        return log;
    }

    @Override
    public int updateState(String examId, String candidateId, String itemId, Integer flagState) {
        return candidateAnswerMapper.updateState(examId, candidateId, itemId, flagState);
    }

    /**
     * 计算分数忽略迫选题
     * @param param
     */
    @Override
    public void calcObjectiveScore(CandidateFinishExamVO param, boolean forceFinishExam){
        QueryWrapper<CandidateProcess> query = Wrappers.query();
        query.lambda().select(CandidateProcess::getObjectiveScore, CandidateProcess::getRedoExamCount,
                        CandidateProcess::getId, CandidateProcess::getStartedExam, CandidateProcess::getExamStatus,
                        CandidateProcess::getMaxScoreVer, CandidateProcess::getPaperId, CandidateProcess::getPaperVersion,
                        CandidateProcess::getAdmissionNumber)
                .eq(CandidateProcess::getExamId, param.getExamId())
                .eq(CandidateProcess::getCandidateId, param.getCandidateId()).eq(CandidateProcess::getDelFlag, false);
        // 查询考生信息
        CandidateProcess info = iCandidateProcessService.getOne(query);
        if(info == null || info.getExamStatus().intValue() >= CandidateStatusCode.COMMIT.getCode().intValue()) {
            // 缺考考生忽略
            return;
        }

        //baseMapper.calcObjectiveScore(param);

        List<Integer> paperTypeList = new ArrayList<>();
        paperTypeList.add(PaperTypeCode.NORMAL.getCode());
        paperTypeList.add(PaperTypeCode.XIAOTIJISHI.getCode());
        QueryWrapper<CandidatePaper> queryCandidatePaper = Wrappers.query();
        queryCandidatePaper.lambda().select(CandidatePaper::getId, CandidatePaper::getPaperId, CandidatePaper::getPaperVersion)
                .eq(CandidatePaper::getExamId, param.getExamId())
                .eq(CandidatePaper::getCandidateId, param.getCandidateId())
                .in(CandidatePaper::getPaperType, paperTypeList);
        List<CandidatePaper> candidatePaperList = iCandidatePaperService.list(queryCandidatePaper);
        // 统分
        iScoreService.calcObjectiveCandidateAnswerList(param.getExamId(), param.getCandidateId(), candidatePaperList, null, 0);

        //获取本次客观题考试总成绩
        /*
        BigDecimal objectiveScore = baseMapper.getObjectiveScore(param);
        if (objectiveScore == null) {
            objectiveScore = new BigDecimal(0);
        }
         */
        BigDecimal objectiveScore = iScoreService.getCandidateObjectScore(param.getExamId(), param.getCandidateId(), 0);
        CandidateProcess candidateProcess = new CandidateProcess();
        candidateProcess.setId(info.getId());
        candidateProcess.setObjectiveScore(objectiveScore);
        candidateProcess.setMaxScoreVer(info.getRedoExamCount());
        candidateProcess.setEndTime(LocalDateTime.now());
        candidateProcess.setExamId(param.getExamId());
        if(param.getFinishType() != null) {
            candidateProcess.setFinishType(param.getFinishType());
        }
        if(param.getChangScreenNumber() != null) {
            candidateProcess.setChangScreenNumber(param.getChangScreenNumber());
        }

        Integer oldState = info.getExamStatus();
        // 默认完成
        Integer state = CandidateStatusCode.COMMIT.getCode().intValue();
        if (oldState.intValue() == CandidateStatusCode.LOGIN.getCode().intValue()
                && ((info.getStartedExam() != null && !info.getStartedExam()) || info.getPaperId() == null)) {
            // 考生未初始化试卷，则是因故未完成，
            state = CandidateStatusCode.NOT_COMPLETE_BY_REASON.getCode().intValue();
        } else if (oldState.intValue() == CandidateStatusCode.UN_LOGIN.getCode().intValue()) {
            // 未登录，则缺考
            state = CandidateStatusCode.ABSENT.getCode().intValue();
        }
        candidateProcess.setExamStatus(state);

        //获取项目参数，练习模式用
        List<ExamConfigVO> examConfigVOList = iExamCacheService.getExamConfigCache(param.getExamId());
        //是否保留最高分
        boolean keepHighestScore = examConfigVOList.stream().anyMatch(examConfigVO ->
                Constants.KEEP_HIGHEST_SCORE.equalsIgnoreCase(examConfigVO.getConfigCode()));
        //分数保留最大值，与上次比较
        if (keepHighestScore && info.getObjectiveScore() != null &&
                info.getObjectiveScore().compareTo(objectiveScore) == 1) {
            // 上次的分数大于当次分数，保留上次的分数和版本号
            candidateProcess.setObjectiveScore(info.getObjectiveScore());
            candidateProcess.setMaxScoreVer(info.getMaxScoreVer());
        }
        //更新每个单元得分
        this.updateCandidatePaperScore(param);
        // 更新考生信息
        iCandidateProcessService.updateById(candidateProcess);
        //iCandidateProcessService.updateCandidateProcess(candidateProcess, query);
        CandidateInfoUpdateDTO candidateInfoUpdateVO = CandidateInfoUpdateDTO.builder()
                .examId(param.getExamId()).candidateId(param.getCandidateId())
                .admissionNumber(info.getAdmissionNumber())
                .paperId(info.getPaperId()).paperVersion(info.getPaperVersion())
                .objectiveScore(objectiveScore).examEndDate(LocalDateTime.now())
                .state(candidateProcess.getExamStatus())
                .finishState(candidateProcess.getFinishType()).build();
        //临时加的线程，用与模拟请求超时
        /*try {
            log.info("提交试卷，线程模拟请求超时开始");
            TimeUnit.SECONDS.sleep(configManager.getThreadFinishSleep());
            log.info("提交试卷，线程模拟请求超时结束");
        } catch (Exception e){
            log.error("提交试卷，模拟请求超时线程异常",e);
        }
        if(configManager.getThreadFinishException() == 1){
            log.info("提交试卷,模拟报异常了-----------------");
            throw new RuntimeException();
        }*/
        // 通知考务更新考生信息
        this.afterFinishCallKwInterface(candidateInfoUpdateVO);
    }

    private void afterFinishCallKwInterface(CandidateInfoUpdateDTO candidateInfoUpdateVO){
        // 通知考务更新状态
        iCandidateProcessService.updateKwCandidateInfo(candidateInfoUpdateVO);
        kwClient.updateCandidateOnlineExamState(candidateInfoUpdateVO.getExamId(),
                candidateInfoUpdateVO.getCandidateId(), false, candidateInfoUpdateVO.getState());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public FinishVO finishExam(CandidateFinishExamVO param, HttpServletRequest request) {
        if(param.getFinishType().intValue() == 5) {
            param.setFinishType(3);
        }
        FinishVO finishVO = FinishVO.builder().hasRemainTime(false).finishExam(false).build();
        if(param.getFinishAllPaper() != null && param.getFinishAllPaper()){
            this.finish(param, finishVO,request);
        } else {
            // 查询还有多少单元未做完
            List<CandidatePaper> paperList = iCandidatePaperService.list(Wrappers.<CandidatePaper>lambdaQuery()
                    .select(CandidatePaper::getMixSort, CandidatePaper::getPaperType, CandidatePaper::getPaperId,
                            CandidatePaper::getPaperVersion)
                    .eq(CandidatePaper::getExamId, param.getExamId())
                    .eq(CandidatePaper::getCandidateId, param.getCandidateId())
                    .gt(CandidatePaper::getMixSort, 0)
                    .eq(CandidatePaper::getFinish, false)
                    .orderByAsc(CandidatePaper::getMixSort));
            // 强制收卷时触发
            if(paperList.size() == 0){
                this.finish(param, finishVO,request);
            } else {
                this.finishCheck(param.getExamId(), param.getCandidateId(), paperList.get(0).getPaperType(),
                        paperList.get(0).getPaperId(), paperList.get(0).getPaperVersion());
                if (paperList.size() == 1 &&
                        paperList.get(0).getMixSort().intValue() == param.getPaperSort().intValue()) {
                    // 当前交卷的单元等于最后为结束的单元，则结束本场考试
                    this.finish(param, finishVO,request);
                } else {
                    this.logRecord(param, request);
                    // 标记该本单元已经做完
                    this.updateCandidatePaperFinishStatus(param);
                    //
                    if (paperList.size() == 1) {
                        finishVO.setPaperType(paperList.get(0).getPaperType());
                    } else {
                        finishVO.setPaperType(paperList.get(1).getPaperType());
                    }
                }
            }
        }
        return finishVO;
    }

    /**
     * 性格测试卷交卷前检查，是否达到交卷标准
     * @param examId
     * @param candidateId
     * @param paperType
     * @param paperId
     * @param paperVersion
     */
    private void finishCheck(String examId, String candidateId, int paperType, String paperId, String paperVersion){
        // 是否有剩余时长，如果有，性格测试卷不允许交卷
        if (PaperTypeCode.CHARACTER.getCode().intValue() == paperType) {
            // 查询是否作答完成
            int count = candidateAnswerMapper.selectCount(Wrappers.<CandidateAnswer>lambdaQuery()
                    .eq(CandidateAnswer::getExamId, examId)
                    .eq(CandidateAnswer::getCandidateId, candidateId)
                    .eq(CandidateAnswer::getPaperVersion, paperId)
                    .eq(CandidateAnswer::getPaperVersion, paperVersion)
                    .isNull(CandidateAnswer::getObjectiveAnswer));
            if (count > 0) {
                throw new KsBizRunTimeException(BizStatusCode.CHARACTER_NOT_ALLOW_FINISH.getCode(),
                        BizStatusCode.CHARACTER_NOT_ALLOW_FINISH.getMsg());
            }
        }
    }

    /**
     * 结束本场考试
     * @param param
     * @param finishVO
     */
    private void finish(CandidateFinishExamVO param, FinishVO finishVO,HttpServletRequest request){
        // 1.计算客观题分数，按考生来计算，不区分试卷和试卷包
        this.calcObjectiveScore(param, false);
        // 2.记录操作日志
        this.logRecord(param, request);
        finishVO.setFinishExam(true);
    }

    private void logRecord(CandidateFinishExamVO param, HttpServletRequest request){
        try {
            CandidatePaper candidatePaper = iCandidatePaperService.getOne(Wrappers.<CandidatePaper>lambdaQuery()
                            .select(CandidatePaper::getPaperId, CandidatePaper::getPaperVersion, CandidatePaper::getFinish)
                    .eq(CandidatePaper::getExamId, param.getExamId())
                    .eq(CandidatePaper::getCandidateId, param.getCandidateId())
                    .eq(CandidatePaper::getMixSort, param.getPaperSort()));

            Integer type = null;
            if(param.getFinishType() == null || FinishExamType.MANUAL.getCode().intValue() == param.getFinishType().intValue()){
                type = CandidateLogOperateType.COMMIT_PAPER.getCode();
            } else if(FinishExamType.FORCE.getCode().intValue() == param.getFinishType().intValue()){
                type = CandidateLogOperateType.COMMIT_PAPER_BY_FORCE.getCode();
            } else if(FinishExamType.OVER_SCREEN.getCode().intValue() == param.getFinishType().intValue()){
                type = CandidateLogOperateType.CANCEL_FINISH_BY_OVER_SCREEN.getCode();
            } else if(FinishExamType.OVER_SCREEN_TIME_OUT.getCode().intValue() == param.getFinishType().intValue()){
                type = CandidateLogOperateType.CANCEL_FINISH_BY_OVER_SCREEN_TIME_OUT.getCode();
            } else {
                type = CandidateLogOperateType.COMMIT_PAPER_BY_NO_TIME.getCode();
            }
            if(candidatePaper.getFinish() && FinishExamType.FORCE.getCode().intValue() == param.getFinishType().intValue()){
                return;
            }

            iLogService.finishExamLog(param.getExamId(), param.getCandidateId(), candidatePaper.getPaperId(),
                    candidatePaper.getPaperVersion(), param.getFinishType());

            } catch (Exception e) {
            log.error("{}", e);
        }
    }

    private void updateCandidatePaperFinishStatus(CandidateFinishExamVO param){
        LambdaUpdateWrapper<CandidatePaper> updateWrapper = Wrappers.<CandidatePaper>lambdaUpdate()
                .eq(CandidatePaper::getExamId, param.getExamId())
                .eq(CandidatePaper::getCandidateId, param.getCandidateId())
                .eq(CandidatePaper::getMixSort, param.getPaperSort())
                .set(CandidatePaper::getFinish, true);
        // 标记该单元已经做完
        iCandidatePaperService.update(updateWrapper);
    }

    private void updateCandidatePaperScore(CandidateFinishExamVO param) {
        List<CandidatePaper> candidatePapers = CollectionUtil.isEmpty(param.getPaperList()) ? iCandidatePaperService.lambdaQuery()
                .select(CandidatePaper::getId, CandidatePaper::getPaperId, CandidatePaper::getPaperVersion,
                        CandidatePaper::getExamUseTime, CandidatePaper::getAccessTime, CandidatePaper::getPaperTimeLength,
                        CandidatePaper::getRemainingTime, CandidatePaper::getExtraTime)
                .eq(CandidatePaper::getExamId, param.getExamId()).eq(CandidatePaper::getCandidateId,
                        param.getCandidateId()).list() : param.getPaperList();

        candidatePapers.stream().forEach(item -> {
            //BigDecimal objectiveScore = baseMapper.getObjectiveScoreByPaper(param.getExamId(), param.getCandidateId(),
              //      item.getPaperId(), item.getPaperVersion());
            BigDecimal objectiveScore = iScoreService.getCandidatePaperObjectScore(param.getExamId(), param.getCandidateId(), item.getPaperId(), item.getPaperVersion(), 0);
            if (objectiveScore == null) {
                objectiveScore = new BigDecimal(0);
            }
            CandidatePaper candidatePaper = new CandidatePaper();
            candidatePaper.setId(item.getId());
            candidatePaper.setObjectiveScore(objectiveScore);
            candidatePaper.setFinish(true);
            long currentTimeMillis = System.currentTimeMillis();
            if (item.getAccessTime() == null) {
                //首次访问 AccessTime可能为空
                candidatePaper.setAccessTime(currentTimeMillis);
                item.setAccessTime(currentTimeMillis);
            }
            candidatePaper.setExamUseTime(item.getPaperTimeLength() + item.getExtraTime() - item.getRemainingTime());
            iCandidatePaperService.updateById(candidatePaper);
        });
    }

    @Override
    public List<SubjectiveAnswerVO> getSubjectiveAnswerByExam(String examId, String admissionNumber, Integer isBackup) {
        //根据考试id获取已交卷/已完成考生主观题答案
        List<SubjectiveAnswerVO> subjectiveAnswerList;
        if(Objects.equals(0,isBackup)){
            subjectiveAnswerList = candidateAnswerMapper.getSubjectiveAnswerByExam(examId, admissionNumber, CandidateStatusCode.COMMIT.getCode());
        } else {
            subjectiveAnswerList = candidateAnswerMapper.getSubjectiveAnswerByExamWhenBackup(examId, admissionNumber, CandidateStatusCode.COMMIT.getCode());
        }
        return subjectiveAnswerList;
    }

    @Override
    public List<CandidateAnswer> selectAnswerList(String examId, String admissionNumber) {
        return baseMapper.selectAnswerList(examId, admissionNumber);
    }

    @Override
    public XyEvalResultVO getXyEvalResult(String examId, String candidateId, String paperId, String paperVersion, List<ExamCandidateDetailInfoForScoreVO> examCandidateDetailInfos) {
        //获取考生配置信息
        UserInfoVO userInfo = new UserInfoVO();
        userInfo.setUserId(candidateId);
        for (ExamCandidateDetailInfoForScoreVO examCandidateDetailInfo : examCandidateDetailInfos) {
            if (examCandidateDetailInfo.getComponentName().equals(CollectInfoComponentNameCode.CANDIDATE_CONFIG_NAME.getCode())) {
                userInfo.setName(examCandidateDetailInfo.getValue());
            } else if (examCandidateDetailInfo.getComponentName().equals(CollectInfoComponentNameCode.CANDIDATE_CONFIG_SEX.getCode())) {
                String sex = examCandidateDetailInfo.getValue().equals("0") ? "1" : "2";
                userInfo.setGender(Integer.parseInt(sex));
            } else if ("出生年月".equals(examCandidateDetailInfo.getFieldName())) {
                String birthday = examCandidateDetailInfo.getValue();
                birthday = birthday.replace("年", "-")
                        .replace("月", "-")
                        .replace("日", "");
                userInfo.setBirthday(birthday);
            } else if ("毕业院校".equals(examCandidateDetailInfo.getFieldName())) {
                userInfo.setCollage(examCandidateDetailInfo.getValue());
            } else if ("硕士毕业院校".equals(examCandidateDetailInfo.getFieldName()) && StringUtils.isNotBlank(examCandidateDetailInfo.getValue())) {
                userInfo.setCollage(examCandidateDetailInfo.getValue());
            } else if ("本科毕业院校".equals(examCandidateDetailInfo.getFieldName()) && StringUtils.isNotBlank(examCandidateDetailInfo.getValue()) && StringUtils.isBlank(userInfo.getCollage())) {
                userInfo.setCollage(examCandidateDetailInfo.getValue());
            } else if ("学历".equals(examCandidateDetailInfo.getFieldName())) {
                userInfo.setEducation(examCandidateDetailInfo.getValue());
            } else if ("专业".equals(examCandidateDetailInfo.getFieldName())) {
                userInfo.setSpecialty(examCandidateDetailInfo.getValue());
            } else if ("硕士专业".equals(examCandidateDetailInfo.getFieldName()) && StringUtils.isNotBlank(examCandidateDetailInfo.getValue())) {
                userInfo.setSpecialty(examCandidateDetailInfo.getValue());
            } else if ("本科专业".equals(examCandidateDetailInfo.getFieldName()) && StringUtils.isNotBlank(examCandidateDetailInfo.getValue()) && StringUtils.isBlank(userInfo.getSpecialty())) {
                userInfo.setSpecialty(examCandidateDetailInfo.getValue());
            } else if ("毕业时间".equals(examCandidateDetailInfo.getFieldName())) {
                String graduationTime = examCandidateDetailInfo.getValue();
                graduationTime = graduationTime.replace("年", "-")
                        .replace("月", "-")
                        .replace("日", "");
                userInfo.setGraduationDate(graduationTime);
            } else if ("籍贯".equals(examCandidateDetailInfo.getFieldName())) {
                userInfo.setNativePlace(examCandidateDetailInfo.getValue());
            } else if (examCandidateDetailInfo.getComponentName().equals(CollectInfoComponentNameCode.CANDIDATE_CONFIG_IDCARD.getCode())) {
                userInfo.setIdentificationNumber(examCandidateDetailInfo.getValue());
            }
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            userInfo.setExamDate(LocalDateTime.now().format(dtf));            
        }
        Boolean requiredColumn = true;
        if (StringUtils.isBlank(userInfo.getName())
                || StringUtils.isBlank(userInfo.getBirthday())
                || userInfo.getGender() == null) {
            requiredColumn = false;
        }
        List<ExamResult> results = new ArrayList<ExamResult>();
        //判断考生答案是走哪一个库
        String ecRsp = kwClient.getExamCandidateById(candidateId);
        log.info("调用考务系统获取考生信息，：{}",ecRsp);
        ApiResponse<ExamCandidate> ecResponse = ApiResponse.ConvertRet(ecRsp,ExamCandidate.class);
        if(!Objects.equals(ecResponse.getCode(),BizStatusCode.KW_SUCCESS_OK.getCode()) || Objects.isNull(ecResponse.getData())){
            log.error("调用考务系统获取考生信息失败，返回信息为:{}",ecResponse);
            throw new KsBizRunTimeException();
        }
        ExamCandidate ec = ecResponse.getData();
        List<CandidateAnswer> candidateAnswerList;
        if(Objects.equals(0,ec.getIsBackup())){
            QueryWrapper<CandidateAnswer> query = Wrappers.query();//todo
            query.lambda().eq(CandidateAnswer::getCandidateId, candidateId)
                    .eq(CandidateAnswer::getExamId, examId)
                    .eq(CandidateAnswer::getPaperId, paperId)
                    .eq(CandidateAnswer::getPaperVersion, paperVersion)
                    .eq(CandidateAnswer::getDelFlag, false);
            candidateAnswerList = super.list(query);
        } else {
            candidateAnswerList = iCandidateAnswerBackupService.getXyEvalResult(candidateId,examId,paperId,paperVersion);
        }

        // 如果某道题目答案为空，则不调用测评接口
        Boolean hasBlankAnswer = false;
        for (CandidateAnswer candidateAnswer : candidateAnswerList) {
            if (StringUtils.isBlank(candidateAnswer.getObjectiveAnswer())) {
                hasBlankAnswer = true;
                break;
            }
            ExamResult examResult = new ExamResult();
            examResult.setOption(candidateAnswer.getObjectiveAnswer());
            examResult.setDisturbOption(candidateAnswer.getForceAnswer());
            String itemSerialNumber = candidateAnswer.getItemSerialNumber();
            if (StringUtils.isNotBlank(itemSerialNumber)) {
                String[] itemSerialNumbers = itemSerialNumber.split("-");
                examResult.setQuestionGroupId(itemSerialNumbers[0]);
                examResult.setQuestionBankId(itemSerialNumbers[1]);
                examResult.setQuestionId(itemSerialNumbers[2]);                
            }
            Long time = candidateAnswer.getAnswerTime();
            time = (long) Math.ceil(time/(double)1000); 
            examResult.setTime(time);
            results.add(examResult);
        }
        XyEvalRequestVO evalRequest = new XyEvalRequestVO();
        evalRequest.setUserInfo(userInfo);
        evalRequest.setResults(results);
        evalRequest.setType(results.size() == configManager.getGuideTypeSchool().intValue() ? 0 : 1);
        String json = JSON.toJSON(evalRequest).toString();
        XyEvalResultVO xyEvalResult = new XyEvalResultVO();
        if (!hasBlankAnswer && requiredColumn) {
            //String resResult = HttpClientUtil.doPostJson(configManager.getXyEvalRequestUrl(), json);
            log.info("获取测评报告请求参数：{}", json);
            String resResult = tpsClient.getXyEvalResult(configManager.getXyEvalRequestUrl(), json);
            log.info("获取测评报告请求参数：{}", json);
            try {
                xyEvalResult = JSONObject.parseObject(resResult, XyEvalResultVO.class);
             } catch (Exception ex) {
                xyEvalResult.setMessage(resResult);
             }
             if (xyEvalResult.getCode() == null) {
                 xyEvalResult.setMessage(resResult);
             }
        } else {
            xyEvalResult.setCode("-1");
            if (!requiredColumn) {
                xyEvalResult.setMessage("该考生姓名、性别或出生日期为空");
            } else {
                xyEvalResult.setMessage("该考生性格测试卷未作答完成");
            }
        }
        this.insertEvalLog(examId, candidateId, configManager.getXyEvalRequestUrl(), json, xyEvalResult);
        return xyEvalResult;
    }
    
    private void insertEvalLog(String examId, String candidateId, String requestUrl, String requestJson, XyEvalResultVO xyEvalResult) {
        XyEvalLog xyEvalLog = new XyEvalLog();
          xyEvalLog.setId(UUIDUtils.randomUUID());
          xyEvalLog.setExamId(examId);
          xyEvalLog.setCandidateId(candidateId);
          xyEvalLog.setRequestJson(requestJson);
          xyEvalLog.setRequestUrl("-1".equals(xyEvalResult.getCode()) ? "" : requestUrl); 
          xyEvalLog.setResultData(JSON.toJSON(xyEvalResult).toString());
          xyEvalLog.setResultStatusCode(xyEvalResult.getCode());
          xyEvalLog.setCreateBy(candidateId);
          xyEvalLog.setCreateDate(LocalDateTime.now());
          xyEvalLogMapper.insert(xyEvalLog);
    }
    
    @Override
    public Boolean isGetEvalReport(String examId, String candidateId) {
        QueryWrapper<XyEvalLog> query = Wrappers.query();
         query.lambda().eq(XyEvalLog::getCandidateId, candidateId)
                 .eq(XyEvalLog::getExamId, examId)
                 .eq(XyEvalLog::getResultStatusCode, "200")
                 .eq(XyEvalLog::getDelFlag, false);
         return xyEvalLogMapper.selectCount(query) > 0;
    }
    
    @Override
    public DayiAssessResultVO sendScoreToDayi(String examId, String candidateId, String admissionNumber, BigDecimal totalScore, List<ExamCandidateDetailInfoForScoreVO> examCandidateDetailInfos) {
        DayiAssessResultVO dayiAssessResult = new DayiAssessResultVO();
        //获取测评报告
        QueryWrapper<XyEvalLog> query = Wrappers.query();
        query.lambda().eq(XyEvalLog::getCandidateId, candidateId)
                 .eq(XyEvalLog::getExamId, examId)
                 .eq(XyEvalLog::getResultStatusCode, "200")
                 .eq(XyEvalLog::getDelFlag, false); 
        XyCandidateReportVO xyCandidateReport = new XyCandidateReportVO();
        String reportUrl = "";
        List<XyEvalLog> xyEvalLogList = xyEvalLogMapper.selectList(query);
        if (xyEvalLogList.size() > 0) {
            XyEvalLog xyEvalLog = xyEvalLogMapper.selectList(query).get(0);
            XyEvalResultVO xyEvalResult = JSONObject.parseObject(xyEvalLog.getResultData(), XyEvalResultVO.class);
            xyCandidateReport = getXyCandidateReport(xyEvalResult);
            reportUrl = xyEvalResult.getData().getReportUrl();
        }
        xyCandidateReport.setExamId(examId);
        xyCandidateReport.setExamCandidateId(candidateId);
        String token = getDayiToken();
        if ("".equals(token)) {
            xyCandidateReport.setSendScoreDayi(false);
            kwClient.saveXyCandidateReport(JSON.toJSON(xyCandidateReport).toString());
            return dayiAssessResult;
        }
        DayiAssessRequestVO dayiAssessRequest = new DayiAssessRequestVO();
        dayiAssessRequest.setExamId(examId);
        dayiAssessRequest.setAccountId(admissionNumber);
        dayiAssessRequest.setTotalScore(totalScore.floatValue());
        dayiAssessRequest.setReportUrl(reportUrl);
        dayiAssessRequest.setIsFinish(0);
        //获取考生信息
        for (ExamCandidateDetailInfoForScoreVO examCandidateDetailInfo : examCandidateDetailInfos) {
            if (examCandidateDetailInfo.getComponentName().equals(CollectInfoComponentNameCode.CANDIDATE_CONFIG_NAME.getCode())) {
                dayiAssessRequest.setName(examCandidateDetailInfo.getValue());
            } else if (examCandidateDetailInfo.getComponentName().equals(CollectInfoComponentNameCode.CANDIDATE_CONFIG_PHONE.getCode())) {
                dayiAssessRequest.setPhone(examCandidateDetailInfo.getValue());
            } else if (examCandidateDetailInfo.getComponentName().equals(CollectInfoComponentNameCode.CANDIDATE_CONFIG_EMAIL.getCode())) {
                dayiAssessRequest.setEmail(examCandidateDetailInfo.getValue());
            } else if ("应聘id".equals(examCandidateDetailInfo.getFieldName().toLowerCase())
                    && StringUtils.isNotBlank(examCandidateDetailInfo.getFieldName())) {
                dayiAssessRequest.setApplyId(Integer.parseInt(examCandidateDetailInfo.getValue()));
            }
        }
        List<ExamConfigVO> examConfigList = iExamCacheService.getExamConfigCache(examId);
        Optional<ExamConfigVO> examConfigPassScore = examConfigList.stream()
                .filter(ecl -> ExamConfigCode.EXAM_CONFIG_PASS_SCORE.getCode().equals(ecl.getConfigCode()))
                .findFirst();
        String passScore = examConfigPassScore.isPresent() ? examConfigPassScore.get().getConfigValue() : "";
        Boolean passStatus = StringUtils.isNotBlank(passScore) ? totalScore.compareTo(new BigDecimal(passScore)) > -1 && StringUtils.isNotBlank(reportUrl) : true;
        dayiAssessRequest.setPassStatus(passStatus ? 0 : 1);
        String json = JSON.toJSON(dayiAssessRequest).toString();
        String url = configManager.getDayiAssessRequestUrl() + "/auth/assess/saveAssessResult";
        //String resResult = HttpClientUtil.doPostJson(url, json, token);
        String resResult = tpsClient.sendScoreToDayi(url, json, token);

        try {
            dayiAssessResult = JSONObject.parseObject(resResult, DayiAssessResultVO.class);
        } catch (Exception e) {
            log.info("成绩推送大易失败 ,接口请求返回结果：{}, 异常信息： {}", resResult, e);
        }
        xyCandidateReport.setSendScoreDayi(dayiAssessResult.getState() != null && dayiAssessResult.getState().intValue() == 0);

        kwClient.saveXyCandidateReport(JSON.toJSON(xyCandidateReport).toString());
        //推送大易日志
        XyDayiLog dayiLog = new XyDayiLog();
        dayiLog.setId(UUIDUtils.randomUUID());
        dayiLog.setExamId(examId);
        dayiLog.setCandidateId(candidateId);
        dayiLog.setToken(token);
        dayiLog.setRequestJson(json);
        dayiLog.setRequestUrl(configManager.getDayiAssessRequestUrl());
        dayiLog.setResultData(JSON.toJSON(dayiAssessResult).toString());
        dayiLog.setResultStatusCode(dayiAssessResult.getState().toString());
        dayiLog.setCreateBy(candidateId);
        dayiLog.setCreateDate(LocalDateTime.now());
        xyDayiLogMapper.insert(dayiLog);

        return dayiAssessResult;
    }
    
    @Override
    public XyCandidateReportVO getXyCandidateReport(XyEvalResultVO xyEvalResult) {
        XyCandidateReportVO xyCandidateReport = new XyCandidateReportVO();
        xyCandidateReport.setGetEvalReoprt(true);
        xyCandidateReport.setReportUrl(Base64.getEncoder().encodeToString(xyEvalResult.getData().getReportUrl().getBytes(StandardCharsets.UTF_8)));
        ReportContentVO reportContent = xyEvalResult.getData().getReportContent();
        OverviewInfoVO overviewInfo = reportContent.getOverviewInfoVo();
        xyCandidateReport.setEmotionInclinationScore(overviewInfo.getEmotionScore());
        xyCandidateReport.setEmotionInclinationEvaluate(overviewInfo.getEmotionLevel());
        xyCandidateReport.setEvalValidScore(overviewInfo.getConfidenceScore());
        xyCandidateReport.setEvalValidEvaluate(overviewInfo.getConfidenceLevel());
        DimensionInfoVO dimensionInfo = reportContent.getDimensionInfoVo();
        for (EmotionVO emotion : dimensionInfo.getEmotion()) {
            if("焦虑性".equals(emotion.getDimensionTitle())) {
                xyCandidateReport.setAnxietyScore(emotion.getScore());
            } else if("紧张性".equals(emotion.getDimensionTitle())) {
                xyCandidateReport.setTensityScore(emotion.getScore());
            } else if("拘谨性".equals(emotion.getDimensionTitle())) {
                xyCandidateReport.setPrudenceScore(emotion.getScore());
            } else if("怀疑性".equals(emotion.getDimensionTitle())) {
                xyCandidateReport.setSkepticismScore(emotion.getScore());
            } else if("怯弱性".equals(emotion.getDimensionTitle())) {
                xyCandidateReport.setCowardiceScore(emotion.getScore());
            } else if("冲突性".equals(emotion.getDimensionTitle())) {
                xyCandidateReport.setConflictScore(emotion.getScore());
            }
        }
        for (CompetencyVO competency : dimensionInfo.getCompetency()) {
            if("沟通理解".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setCommunicationScore(competency.getScore());
            } else if("问题解决".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setSolveScore(competency.getScore());
            } else if("团队合作".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setTeamsScore(competency.getScore());
            } else if("积极主动".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setAggressivenessScore(competency.getScore());
            } else if("适应能力".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setAdaptabilityScore(competency.getScore());
            } else if("学习成长".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setStudyScore(competency.getScore());
            } else if("信任他人".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setTrustOthersScore(competency.getScore());
            } else if("自信果敢".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setConfidentScore(competency.getScore());
            } else if("抗压能力".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setPressureResistanceScore(competency.getScore());
            } else if("创造能力".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setCreativeAbilityScore(competency.getScore());
            } else if("成就动机".equals(competency.getDimensionTitle())) {
                xyCandidateReport.setAchievementMotivationScore(competency.getScore());
            }
        }
        return xyCandidateReport;
    }
    
    /*
     * 获取大易token
     */
    private String getDayiToken() {
        String url = configManager.getDayiAssessRequestUrl() + "/getToken";
        DayiTokenRequestVO dayiTokenRequest = new DayiTokenRequestVO();
        dayiTokenRequest.setCorpCode(configManager.getDayiCorpCode());
        dayiTokenRequest.setUserName(configManager.getDayiUserName());
        dayiTokenRequest.setPassword(configManager.getDayiPassword());

        String json = JSON.toJSON(dayiTokenRequest).toString();
        //String resResult = HttpClientUtil.doPostJson(url, json);
        String resResult = tpsClient.getDayiToken(url, json);
        DayiTokenResultVO dayiTokenResult = new DayiTokenResultVO();
        try {
            dayiTokenResult = JSONObject.parseObject(resResult, DayiTokenResultVO.class);
        } catch (Exception e) {
            log.info("获取大易token失败 ,接口请求返回结果：{}, 异常信息： {}", resResult, e);
            return "";
        }
        if (dayiTokenResult.getState() != null &&  dayiTokenResult.getState().intValue() == 0) {
            return dayiTokenResult.getData().getToken();
        }
        log.info("获取大易token失败 {}", resResult);
        return "";
    }

    /**
     * 检查当前页是否已经答完题，未答完则不保存答案给前端提示
     * @param key
     * @param param
     * @param candidatePaper
     */
    private void checkCharacterAnswer(String key, CandidateQusAnsVO param, CandidatePaper candidatePaper) {
        Object obj = redisTemplate.opsForValue().get(key);
        boolean isAllItemHasAnswer = true;
        // 提交答案为空的数量
        if(param.getAnswer().stream().filter(item -> item.getUserAnswerList() == null
                || item.getUserAnswerList().size() == 0).count() > 0) {
            isAllItemHasAnswer = false;
        } else {
            if(obj == null) {
                // 缓存没有查数据库，正常考试不会走这里
                List<CandidateAnswer> list = super.lambdaQuery()
                        .select(CandidateAnswer::getObjectiveAnswer, CandidateAnswer::getForceAnswer, CandidateAnswer::getTopicId)
                        .eq(CandidateAnswer::getExamId, param.getExamId())
                        .eq(CandidateAnswer::getCandidateId, param.getCandidateId())
                        .eq(CandidateAnswer::getPaperId, candidatePaper.getPaperId())
                        .eq(CandidateAnswer::getPaperVersion, candidatePaper.getPaperVersion())
                        .eq(CandidateAnswer::getDelFlag, false).list();
                long num = list.stream().filter(item -> item.getTopicId().equalsIgnoreCase(param.getTopicId())
                        && item.getObjectiveAnswer() == null).count();
                obj = num;
            }
            if(param.getAnswer().size() != Long.valueOf(obj.toString())) {
                isAllItemHasAnswer = false;
            }
        }
        if(!isAllItemHasAnswer) {
            throw new KsBizRunTimeException(BizStatusCode.NOT_ANSWER_ALL_QUESTION_CURRENT_PAGE.getCode());
        }
    }

    /**
     * 处理：
     *      选择的答案是否存在二次迫选，如都没有选中干扰项，则更新该页为完成状态(ks_paper_topic)
     * 只支持一道大题
     * @param param 参数
     * @param verify
     */
    private PaperTopicItemListVO saveQuestionForCharacterTest(CandidateQusAnsVO param, VerifyAnswerOptionsDTO verify, HttpServletRequest request) {
        CandidatePaper candidatePaper = verify.getCandidatePaper();
        PaperTopicItemListVO paperTopic = new PaperTopicItemListVO();
        // 定义当前页需要作答题数量缓存key
        String cacheKey = KeyDefineCommon.getCandidateNextNeedAnswerNumber(param.getExamId(), param.getCandidateId(),
                candidatePaper.getPaperId(), candidatePaper.getPaperVersion(), param.getTopicId());
        // 检查当前页是否已经答完题，该逻辑原先是在获取下一页，现在改到此处检查
        this.checkCharacterAnswer(cacheKey, param, candidatePaper);
        // 返回需要二次迫选小题
        List<PaperQuestionVO> itemList = this.saveAllQuestion(param, verify, request);
        // 空表示没有二次迫选
        if (itemList == null || itemList.size() == 0) {
            return null;
        } else {
            paperTopic.setItemData(itemList);
            // 循环试卷大题，目前只会装载一道大题下所有小题信息，其他大题忽略
            // 循环小题
            for (PaperQuestionVO paperQuestionVO : itemList) {
                PItemDTO item = iPaperService.getPItem(candidatePaper.getPaperId(),
                        candidatePaper.getPaperVersion(), paperQuestionVO.getItemId());
                CandidateAnswer ca = this.getCandidateAnswer(param.getExamId(), param.getCandidateId(),
                        candidatePaper.getPaperId(), candidatePaper.getPaperVersion(), paperQuestionVO.getItemId());
                this.setPaperItem4New(item, paperQuestionVO, ca);
                paperQuestionVO.setItemSort(ca.getItemMixSort());
            }
            paperTopic.setItemData(paperTopic.getItemData().stream()
                    .sorted(Comparator.comparing(PaperQuestionVO::getItemSort))
                    .collect(Collectors.toList()));
            redisTemplate.opsForValue().set(cacheKey, paperTopic.getItemData().size(), Constants.time, Constants.timeUnit);
            return paperTopic;
        }
    }

    /**
     * 不支持复合题
     * @param item
     * @param paperItem
     * @param ca
     */
    private void setPaperItem4New(PItemDTO item, PaperQuestionVO paperItem, CandidateAnswer ca) {
        paperItem.setItemId(item.getItemId());
        paperItem.setItemType(item.getItemType());
        paperItem.setItemContent(item.getItemContent());
        paperItem.setItemSort(item.getItemSort());
        paperItem.setStandardAnswer(item.getStandardAnswer());
        paperItem.setDescription(item.getItemDescription());
        // 需要转换一下
        Map<String, POptionDTO> optionDTOMap = new HashMap<>();
        item.getOptionMap().forEach((k, v) -> {
            optionDTOMap.put(v.getOptionName(), v);
        });

        // 外套循环是二次迫选项，内循环是试卷所有的选项
        paperItem.getOptionsDataList().forEach(topicOption -> {
            POptionDTO option = optionDTOMap.get(topicOption.getOptionName());
            topicOption.setOptionId(option.getOptionId());
            topicOption.setOptionContent(option.getOptionContent());
            topicOption.setOptionValue(topicOption.getOptionName());
        });
        this.sortItemOption(paperItem, ca);

    }

    private void sortItemOption(PaperQuestionVO itemData, CandidateAnswer ca){
        String[] optionMixSortRule = ca.getOptionMixSortRule().split(",");
        List<TopicOptionVO> optionVOList = new ArrayList<>(itemData.getOptionsDataList().size());
        // 把选项重新排序，按照初始化试卷定的顺序重新排好给前端,只支持迫选-单选
        for (String optionId : optionMixSortRule) {
            for (TopicOptionVO topicOptionVO : itemData.getOptionsDataList()) {
                if(topicOptionVO.getOptionId().equalsIgnoreCase(optionId)){
                    optionVOList.add(topicOptionVO);
                    break;
                }
            }
        }
        itemData.setOptionsDataList(optionVOList);
    }
    
    /**
     * 判断性格测试卷类型
     * @param examId
     * @param candidateId
     * @param paperId
     * @param paperVersion
     * @return 0-校招测试，1-社招测试
     */
    @Override
    public Integer getEvalPaperType(String examId, String candidateId, String paperId, String paperVersion) {
        QueryWrapper<CandidateAnswer> query = Wrappers.query();
         query.lambda().eq(CandidateAnswer::getExamId, examId).eq(CandidateAnswer::getCandidateId, candidateId)
                 .eq(CandidateAnswer::getPaperId, paperId).eq(CandidateAnswer::getPaperVersion, paperVersion)
                 .eq(CandidateAnswer::getDelFlag, false);
         Integer questionCount =  super.count(query);
         return questionCount.intValue() == configManager.getGuideTypeSchool().intValue() ? 0 : 1;
    }
    
    @Override
    public String getInterviewUrl(String examId, String candidateId, String supplierType, String modeId) {
        String token = this.getInterviewToken(supplierType);
        if ("".equals(token)) {
            return "";
        }
        String res = kwClient.getInterviewCandidateInfo(candidateId);
        log.info("获取考务近屿供应商跳转需要的考生信息接口返回结果：【{}】", res);
        @SuppressWarnings("unchecked")
        ApiResponse<JSONObject> apiResponse = JSON.parseObject(res, ApiResponse.class);
        if (apiResponse.getCode().intValue() != BizStatusCode.KW_SUCCESS_OK.getCode().intValue()) {
            throw new NestedBusinessException("获取考务近屿供应商跳转需要的考生信息失败");
        }
        InterviewCandidateInfoVO candidateInfo = JSONObject.parseObject(apiResponse.getData().toJSONString(), InterviewCandidateInfoVO.class);
        candidateInfo.setGender("0".equals(candidateInfo.getGender()) ? "1" : "0");
        String birthday = candidateInfo.getBirthday().replace("/", "-");
        birthday = birthday.substring(0, 10);
        candidateInfo.setBirthday(birthday);
        InterviewUrlRequestVO request = new InterviewUrlRequestVO();
        String url = "";
        if ("interview".equals(supplierType)) {
            url = configManager.getInterviewRequestUrl()
                    + "/open/impaction/candidate?token=" + token;
        } else if ("codingQuestion".equals(supplierType)) {
            url = configManager.getCodingQuestionRequestUrl()
                    + "/open/impaction/candidate?token=" + token;
        }
        request.setModeId(modeId);
        List<InterviewCandidateInfoVO> candidateInfos = new ArrayList<InterviewCandidateInfoVO>();
        candidateInfos.add(candidateInfo);
        request.setCandidates(candidateInfos);
        String json = JSON.toJSON(request).toString();
        log.info("获取近屿供应商跳转地址请求参数：{},", json);
        //String resResult = HttpClientUtil.doPostJson(url, json);
        String resResult = tpsClient.getCodingQuestionUrl(url, json);
        log.info("获取近屿供应商跳转地址请求返回结果：{},", resResult);
        XyJinyuLog xyJinyuLog = new XyJinyuLog();
        xyJinyuLog.setId(UUIDUtils.randomUUID());
        xyJinyuLog.setExamId(examId);
        xyJinyuLog.setCandidateId(candidateId);
        xyJinyuLog.setSupplierType(supplierType);
        xyJinyuLog.setToken(token);
        xyJinyuLog.setRequestJson(json);
        xyJinyuLog.setRequestUrl(url);
        xyJinyuLog.setResultData(resResult);

        xyJinyuLog.setCreateBy(candidateId);
        xyJinyuLog.setCreateDate(LocalDateTime.now());

        InterviewUrlResultDataVO result = new InterviewUrlResultDataVO();
        try {
            result = JSONObject.parseObject(resResult, InterviewUrlResultDataVO.class);
            xyJinyuLog.setResultStatusCode(result.getCode().toString());
            xyJinyuLogMapper.insert(xyJinyuLog);
            if (result.getCode() != null &&  result.getCode().intValue() == 0 && result.getData().size() > 0) {
                return result.getData().get(0).getInterviewUrl();
            }
            log.info("获取近屿供应商跳转地址失败 {}", resResult);
            return "";
        } catch (Exception e) {
            log.info("获取近屿供应商跳转地址失败 ,接口请求返回结果：{}, 异常信息： {}", resResult, e);
            xyJinyuLogMapper.insert(xyJinyuLog);
            return "";
        }
    }
    
    private String getInterviewToken(String supplierType) {
        InterviewTokenRequestVO tokenRequest = new InterviewTokenRequestVO();
        String appId = "";
        String securityKey = "";
        String url = "/open/impaction/accesstoken";
        if ("interview".equals(supplierType)) {
            appId = configManager.getInterviewAppId();
            securityKey = configManager.getInterviewSecret();
            url = configManager.getInterviewRequestUrl() + url;
        } else if ("codingQuestion".equals(supplierType)) {
            appId = configManager.getCodingQuestionAppId();
            securityKey = configManager.getCodingQuestionSecret();
            url = configManager.getCodingQuestionRequestUrl() + url;
        }
        tokenRequest.setAppId(appId);
        tokenRequest.setSecurityKey(securityKey);

        String json = JSON.toJSON(tokenRequest).toString();
        //String resResult = HttpClientUtil.doPostJson(url, json);
        String resResult = tpsClient.getJinyuToken(url, json);
        log.info("获取近屿供应商token请求返回结果：{},", resResult);
        InterviewTokenResultVO tokenResult = new InterviewTokenResultVO();
        try {
            tokenResult = JSONObject.parseObject(resResult, InterviewTokenResultVO.class);
            if (tokenResult.getCode() != null &&  tokenResult.getCode().intValue() == 0) {
                return tokenResult.getData();
            }
            log.info("获取近屿供应商token失败 {}", resResult);
            return "";
        } catch (Exception e) {
            log.info("获取近屿供应商token失败 ,接口请求返回结果：{}, 异常信息： {}", resResult, e);
            return "";
        }
    }

    @Override
    public List<ObjectiveItemScoreDTO> getObjectiveItemScore(String examId, String candidateId, String paperId, String paperVersion) {
        return baseMapper.getObjectiveItemScore(examId, candidateId, paperId, paperVersion);
    }

    /**
     * 验证保存答案时参数+ 题型与答案是否匹配
     * @param answer
     * @return
     */
    @Override
    public VerifyAnswerOptionsDTO verifyAnswerOptions(CandidateQusAnsVO answer){
        VerifyAnswerOptionsDTO result = new VerifyAnswerOptionsDTO();
        //检测项目是否已停用
        if(!iExamCacheService.getPublishExam(answer.getExamId())) {
            result.setIsExamPushed(Boolean.FALSE);
            return result;
        }
        // 查询考生试卷
        CandidatePaper candidatePaper = iPaperService.getCandidatePaperFromCache(answer.getExamId(),
                answer.getCandidateId(), answer.getPaperSort());
        if(Objects.isNull(candidatePaper)){
            throw new KsBizRunTimeException(BizStatusCode.ANSWER_PAPER_EXISTS.getCode());
        }
        result.setCandidatePaper(candidatePaper);

        //查询考生的过程表记录用与判断是否交卷
        CandidateProcess candidateProcess = iCandidateProcessService.getOne(Wrappers.<CandidateProcess>lambdaQuery()
                .select(CandidateProcess::getExamStatus, CandidateProcess::getStartedExam)
                .eq(CandidateProcess::getExamId, answer.getExamId()).eq(CandidateProcess::getCandidateId, answer.getCandidateId()));
        if(Objects.isNull(candidateProcess)){
            throw new KsBizRunTimeException(BizStatusCode.ANSWER_CANDIDATE_PROCESS_EXISTS.getCode());
        }
        result.setCandidateProcess(candidateProcess);
        if(candidateProcess.getExamStatus() >= CandidateStatusCode.COMMIT.getCode()
                && candidateProcess.getEndTime() !=null && LocalDateTime.now().isAfter(candidateProcess.getEndTime().plusMinutes(5))) {
            result.setIsFinishExamOrOverEndTime(Boolean.TRUE);
            return result;
        }
        //验证题型与答案是否匹配
        List<CandidateAnswer> ansList = super.lambdaQuery()
                .select(CandidateAnswer::getId, CandidateAnswer::getItemId, CandidateAnswer::getParentItemId,CandidateAnswer::getItemType,
                        CandidateAnswer::getAnswerTime, CandidateAnswer::getObjectiveOrSubjective,CandidateAnswer::getOptionMixSortRule)
                .eq(CandidateAnswer::getExamId, answer.getExamId()).eq(CandidateAnswer::getCandidateId, answer.getCandidateId())
                .eq(CandidateAnswer::getPaperId, candidatePaper.getPaperId())
                .eq(CandidateAnswer::getPaperVersion, candidatePaper.getPaperVersion())
                .eq(CandidateAnswer::getDelFlag, false).list();
        Map<String, CandidateAnswer> ansMap = ansList.stream().collect(Collectors.toMap(CandidateAnswer::getItemId, option -> option));
        result.setAnsMap(ansMap);
        verifyAnswerType(answer,result);
        return result;
    }

    /**
     * 用与判断考生的答案  itemType ->  userAnswer是否匹配
     * @param answer
     * @param result
     */
    private void verifyAnswerType(CandidateQusAnsVO answer,VerifyAnswerOptionsDTO result){
        Boolean isMatch = Boolean.FALSE;//保存的答案参数是否符合规定
        try {
            List<QuestionAnswerVO> vos = answer.getAnswer();
            if(CollectionUtil.isEmpty(vos)){
                result.setIsItemTypeMatch(isMatch);
                return;
            }
            for(QuestionAnswerVO vo : vos){
                //单选题目
                if(Objects.equals(vo.getItemType(),PaperItemTypeCode.ITEM_TYPE_SINGLE_CODE.getCode())){
                    Boolean verifyType = singleAndMultTTypeIsMatch(vo,result,vo.getItemType());
                    if(!verifyType){
                        result.setIsItemTypeMatch(isMatch);
                        return;
                    }
                    //判断题
                } else if(Objects.equals(vo.getItemType(),PaperItemTypeCode.ITEM_TYPE_JUDGMENT_CODE.getCode())){
                    //提取出用户的答案
                    Boolean verifyType = judgmentTypeIsMatch(vo,result);
                    if(!verifyType){
                        result.setIsItemTypeMatch(isMatch);
                        return;
                    }
                } else if (Objects.equals(vo.getItemType(),PaperItemTypeCode.ITEM_TYPE_MULTIPLE_CODE.getCode())){
                    Boolean verifyType = singleAndMultTTypeIsMatch(vo,result,vo.getItemType());
                    if(!verifyType){
                        result.setIsItemTypeMatch(isMatch);
                        return;
                    }
                    //匹配题 类似于多选
                } else if (PaperItemTypeCode.ITEM_TYPE_MATCHING_CODE.getCode().equalsIgnoreCase(vo.getItemType())){
                    Boolean verifyType = matchingTypeIsMatch(vo,result, answer.getCandidateId());
                    if(!verifyType){
                        result.setIsItemTypeMatch(isMatch);
                        return;
                    }
                    //不定向选择类似于多选择
                } else if (PaperItemTypeCode.ITEM_TYPE_INDEFINITE_CODE.getCode().equalsIgnoreCase(vo.getItemType())
                        || PaperItemTypeCode.ITEM_TYPE_WEIGHT_CODE.getCode().equalsIgnoreCase(vo.getItemType())){
                    Boolean verifyType = singleAndMultTTypeIsMatch(vo,result,vo.getItemType());
                    if(!verifyType){
                        result.setIsItemTypeMatch(isMatch);
                        return;
                    }
                    //完形填空题，类似于单选
                } else if (PaperItemTypeCode.ITEM_TYPE_CLOZE_FILL_IN_BLANKS_CODE.getCode().equalsIgnoreCase(vo.getItemType())
                        ||PaperItemTypeCode.ITEM_TYPE_CLOZE_FILL_IN_BLANKS_CHILD_CODE.getCode().equalsIgnoreCase(vo.getItemType())){
                    Boolean verifyType = singleAndMultTTypeIsMatch(vo,result,vo.getItemType());
                    if(!verifyType){
                        result.setIsItemTypeMatch(isMatch);
                        return;
                    }
                }
            }
            isMatch = Boolean.TRUE;
            result.setIsItemTypeMatch(isMatch);
        } catch (Exception e){
            log.error("验证题型与答案是否匹配，发生异常:",e);
            result.setIsItemTypeMatch(Boolean.FALSE);
        }
    }

    /**
     * 用与判断是否匹配题答案匹配
     * @param vo
     * @param
     * @return
     */
    private Boolean matchingTypeIsMatch(QuestionAnswerVO vo,VerifyAnswerOptionsDTO result,String candidateId){
        Boolean isMatch = Boolean.TRUE;
        try {
            //提取出用户的答案
            List<AnswerVO> answers = vo.getUserAnswerList();
            if(CollectionUtil.isEmpty(answers)){
                return isMatch;
            }
            CandidateAnswer ans = result.getAnsMap().get(vo.getItemId());
            String mixOptions = ans.getOptionMixSortRule();
            String[] options = mixOptions.split(",");
            List<String> userAnswers = answers.stream().map(AnswerVO::getUserAnswer).collect(Collectors.toList());
            /*
            //匹配题必须都传答案eg A,B,C,E/A,,C,B/A,,,B
            if(userAnswers.size() != options.length){
                isMatch = Boolean.FALSE;
                log.error("匹配题保存上传的答案个数与选项不匹配，考生id:【{}】,试题id:【{}】",candidateId,vo.getItemId());
                return isMatch;
            }
            
             */
            for(String userAnswer : userAnswers){
                //匹配题前端需要把没有选择的选项也要提交上来，会有空的''
                if(StringUtils.isBlank(userAnswer)){
                    continue;
                }
                List<String> byOptionNum = AnswerOptionUtils.getByOptionNum(options.length);
                if(!byOptionNum.contains(userAnswer)){
                    isMatch = Boolean.FALSE;
                    return isMatch;
                }
            }
            return isMatch;
        } catch (Exception e){
            log.error("验证单选题型与答案是否匹配，发生异常:",e);
            isMatch = Boolean.FALSE;
            return isMatch;
        }
    }

    /**
     * 用与判断是否单选题型,多选与答案匹配
     * @param vo
     * @param
     * @return
     */
    private Boolean singleAndMultTTypeIsMatch(QuestionAnswerVO vo,VerifyAnswerOptionsDTO result,String itemType){
        Boolean isMatch = Boolean.TRUE;
        try {
            //提取出用户的答案
            List<AnswerVO> answers = vo.getUserAnswerList();
            if(CollectionUtil.isEmpty(answers)){
                if(Objects.equals(vo.getItemType(),PaperItemTypeCode.ITEM_TYPE_SINGLE_CODE.getCode())){
                    result.setIsSingleNull(Boolean.TRUE);
                }
                return isMatch;
            }
            Set<String> userAnswers = answers.stream().map(AnswerVO::getUserAnswer).collect(Collectors.toSet());
            for(String userAnswer : userAnswers){
                CandidateAnswer ans = result.getAnsMap().get(vo.getItemId());
                String mixOptions = ans.getOptionMixSortRule();
                String[] options = mixOptions.split(",");
                List<String> byOptionNum = AnswerOptionUtils.getByOptionNum(options.length);
                if(!byOptionNum.contains(userAnswer)){
                    isMatch = Boolean.FALSE;
                    return isMatch;
                }
            }
            return isMatch;
        } catch (Exception e){
            log.error("验证单选题型与答案是否匹配，发生异常:",e);
            isMatch = Boolean.FALSE;
            return isMatch;
        }
    }

    /**
     * 用与判断是否判断体型与答案匹配
     * @param vo
     * @param
     * @return
     */
    private Boolean judgmentTypeIsMatch(QuestionAnswerVO vo,VerifyAnswerOptionsDTO result){
        Boolean isMatch = Boolean.TRUE;
        try {
            //提取出用户的答案
            List<AnswerVO> answers = vo.getUserAnswerList();
            if(CollectionUtil.isEmpty(answers)){
                result.setIsJudgmentNull(Boolean.TRUE);
                return isMatch;
            }
            Set<String> userAnswers = answers.stream().map(AnswerVO::getUserAnswer).collect(Collectors.toSet());
            for(String userAnswer : userAnswers){
                List<String> byOptionNum = Arrays.asList("1","0");
                if(!byOptionNum.contains(userAnswer)){
                    isMatch = Boolean.FALSE;
                    return isMatch;
                }
            }
            return isMatch;
        } catch (Exception e){
            log.error("对判断题型进行答案是否匹配，发生异常:",e);
            isMatch = Boolean.FALSE;
            return isMatch;
        }
    }

    /**
     * 当上传的考生答案 与 主观题 不匹配时保存操作轨迹
     * @param ans
     */
    @Override
    public void insertSaveQuestionLog(CandidateQusAnsVO ans){
        List<QuestionAnswerVO> answer = ans.getAnswer();
        if(CollectionUtil.isEmpty(answer)){
            return;
        }
        //用与保存日志容器
        List<CandidateLog> logs = new ArrayList<>(answer.size());
        for(QuestionAnswerVO vo : answer){
            //用与记录操作内容
            String userAnsStr = "";
            List<AnswerVO> userAnswerList = vo.getUserAnswerList();
            if(!CollectionUtil.isEmpty(userAnswerList)){
                userAnswerList.sort(Comparator.comparing(AnswerVO::getUserAnswer));
                //用与暂时记录入参的答案内容
                List<String> answeredList = new ArrayList<>(userAnswerList.size());
                userAnswerList.forEach(item -> answeredList.add(item.getUserAnswer()));
                userAnsStr = StringUtils.join(answeredList, ",");
            }
            logs.add(this.buildLog(ans.getExamId(), vo.getItemId(), ans.getCandidateId(),
                    LogEventType.ANSWER_LOG.getCode(),
                    CandidateAnswerLogType.ANSWER.getCode(), userAnsStr, null, null));
        }
        if(!logs.isEmpty()){
            // 保存轨迹
            //iCandidateLogService.saveBatch(logs);
        }
    }

    @Override
    public List<QuestionAnalysisDTO> getQuestionAnalysis(QuestionAnalysisRequestDTO questionAnalysis) {
        List<QuestionAnalysisDTO> questionAnalysisDTOList = new ArrayList<>();
        String examId = questionAnalysis.getExamId();
        List<String> cheatCandidateIds = questionAnalysis.getCheatCandidateIds();
        QueryWrapper<CandidateProcess> queryCandidateProcess = Wrappers.query();
        queryCandidateProcess.lambda().select(CandidateProcess::getObjectiveScore, CandidateProcess::getCandidateId,  CandidateProcess::getAdmissionNumber)
                .eq(CandidateProcess::getExamId, examId)
                .eq(CandidateProcess::getExamStatus, CandidateStatusCode.COMMIT.getCode().intValue())
                .eq(CandidateProcess::getDelFlag, false);
        List<CandidateProcess> candidateProcessList = iCandidateProcessService.list(queryCandidateProcess);
        if (cheatCandidateIds != null && cheatCandidateIds.size() > 0) {
            candidateProcessList = candidateProcessList.stream().filter(c -> !cheatCandidateIds.contains(c.getCandidateId())).collect(Collectors.toList());
        }
        List<String> totalLowCandidateIds = candidateProcessList.stream().sorted(Comparator.comparing(CandidateProcess::getObjectiveScore).thenComparing(CandidateProcess::getAdmissionNumber)).map(c ->c.getCandidateId()).collect(Collectors.toList());
        //List<String> totalHighCandidateIds = candidateProcessList.stream().sorted(Comparator.comparing(CandidateProcess::getObjectiveScore).thenComparing(CandidateProcess::getAdmissionNumber).reversed()).map(c ->c.getCandidateId()).collect(Collectors.toList());
        List<String> totalHighCandidateIds = candidateProcessList.stream().sorted(Comparator.comparing(CandidateProcess::getObjectiveScore).thenComparing(CandidateProcess::getAdmissionNumber,Comparator.reverseOrder())).map(c ->c.getCandidateId()).collect(Collectors.toList());;

        Integer filterCandidateCount = (int) Math.round(totalLowCandidateIds.size() * 0.27);
        filterCandidateCount = filterCandidateCount.intValue() == 0 ? 1 : filterCandidateCount;
        List<String> lowScoreCandidateIds = totalLowCandidateIds.subList(0, filterCandidateCount.intValue());
        List<String> highScoreCandidateIds = totalHighCandidateIds.subList(totalHighCandidateIds.size() - filterCandidateCount.intValue(), totalHighCandidateIds.size());
        QueryWrapper<CandidateAnswer> queryCandidateAnswer = Wrappers.query();
        queryCandidateAnswer.lambda().select(CandidateAnswer::getItemId, CandidateAnswer::getCandidateId, CandidateAnswer::getScore)
                .eq(CandidateAnswer::getExamId, examId)
                .eq(CandidateAnswer::getDelFlag, false);
        List<CandidateAnswer> candidateAnswerList = super.list(queryCandidateAnswer);
        PaperDataVO paperData = iExamCacheService.getPaperInfoCache(questionAnalysis.getPaperId(), questionAnalysis.getPaperVersion());
        String paperName = paperData.getPaperName();
        Integer validCount = totalLowCandidateIds.size();
        for(TopicDataVO topicData : paperData.getTopicDataList()) {
            List<ItemDataVO> itemDataList = topicData.getItemDataList();
            for (ItemDataVO itemData : itemDataList) {
                if (itemData.getChildItemList() == null || itemData.getChildItemList().size() == 0) {
                    List<CandidateAnswer> candidateAnswerItemList = candidateAnswerList.stream().filter(c -> c.getItemId().equals(itemData.getItemId()) && c.getScore() != null && c.getScore().compareTo(BigDecimal.ZERO) == 1 ).collect(Collectors.toList());
                    Long highCandidatePassCount = candidateAnswerItemList.stream().filter(c -> highScoreCandidateIds.contains(c.getCandidateId())).count();
                    Long lowCandidatePassCount = candidateAnswerItemList.stream().filter(c -> lowScoreCandidateIds.contains(c.getCandidateId())).count();
                    BigDecimal highCandidatePassPercentage = (new BigDecimal(highCandidatePassCount.intValue())).multiply(new BigDecimal(100)).divide(new BigDecimal(highScoreCandidateIds.size()), 4, BigDecimal.ROUND_HALF_UP);
                    BigDecimal lowCandidatePassPercentage = (new BigDecimal(lowCandidatePassCount.intValue())).multiply(new BigDecimal(100)).divide(new BigDecimal(lowScoreCandidateIds.size()), 4, BigDecimal.ROUND_HALF_UP);
                    String range = highCandidatePassPercentage.subtract(lowCandidatePassPercentage) + "%";
                    String difficulty = (new BigDecimal(candidateAnswerItemList.size())).multiply(new BigDecimal(100)).divide(new BigDecimal(validCount), 4, BigDecimal.ROUND_HALF_UP) + "%";
                    QuestionAnalysisDTO row = new QuestionAnalysisDTO();
                    String itemNum =  topicData.getTopicSort() + "大题" + itemData.getItemSort() + "小题";
                    row.setPaperName(paperName);
                    row.setItemNum(itemNum);
                    row.setRange(range);
                    row.setDifficulty(difficulty);
                    row.setValidCount(validCount);
                    questionAnalysisDTOList.add(row);
                } else {
                    for (ItemDataVO childItemData : itemData.getChildItemList()) {
                        List<CandidateAnswer> candidateAnswerItemList = candidateAnswerList.stream().filter(c -> c.getItemId().equals(childItemData.getItemId()) && c.getScore() != null && c.getScore().compareTo(BigDecimal.ZERO) == 1 ).collect(Collectors.toList());
                        Long highCandidatePassCount = candidateAnswerItemList.stream().filter(c -> highScoreCandidateIds.contains(c.getCandidateId())).count();
                        Long lowCandidatePassCount = candidateAnswerItemList.stream().filter(c -> lowScoreCandidateIds.contains(c.getCandidateId())).count();
                        BigDecimal highCandidatePassPercentage = (new BigDecimal(highCandidatePassCount.intValue())).multiply(new BigDecimal(100)).divide(new BigDecimal(highScoreCandidateIds.size()), 4, BigDecimal.ROUND_HALF_UP);
                        BigDecimal lowCandidatePassPercentage = (new BigDecimal(lowCandidatePassCount.intValue())).multiply(new BigDecimal(100)).divide(new BigDecimal(lowScoreCandidateIds.size()), 4, BigDecimal.ROUND_HALF_UP);
                        String range = highCandidatePassPercentage.subtract(lowCandidatePassPercentage) + "%";
                        String difficulty = (new BigDecimal(candidateAnswerItemList.size())).multiply(new BigDecimal(100)).divide(new BigDecimal(validCount), 4, BigDecimal.ROUND_HALF_UP) + "%";
                        QuestionAnalysisDTO row = new QuestionAnalysisDTO();
                        String itemNum =  topicData.getTopicSort() + "大题" + itemData.getItemSort() + "小题" + childItemData.getItemSort() + "子小题";
                        row.setPaperName(paperName);
                        row.setItemNum(itemNum);
                        row.setRange(range);
                        row.setDifficulty(difficulty);
                        row.setValidCount(validCount);
                        questionAnalysisDTOList.add(row);
                    }
                }
            }
        }
        return questionAnalysisDTOList;
    }

    @Override
    public List<CandidateAnswerSimpleVO> selectSimpleAnswerList(String examId, List<String> candidateIds, List<Integer> maxScoreVers) {
        return baseMapper.selectSimpleAnswerList(examId, candidateIds, maxScoreVers);
    }
}
